/**
 * 
 */
package server.room;

import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import server.connection.protocol.Event;
import server.connection.protocol.EventType;
import server.connection.sending.SendingAdapter;
import server.exception.ServerException;
import server.game.LeavetableCause;
import server.user.User;
import server.user.UserManager;

/**
 * 
 * Module resposible for rooms managing
 * 
 * @author Kamil Nowak
 * 
 */
public class RoomManager {
	
	private static final RoomManager instance = new RoomManager();
	
	private final static SendingAdapter SENDING_ADAPTER = SendingAdapter.instance();
	private final static UserManager USER_MANAGER = UserManager.instance();
	
	private final AtomicLong roomNumber = new AtomicLong(0);
	//private final AtomicLong roomNumber = new AtomicLong(0);
	
	private final ConcurrentHashMap<Long,Room> rooms = new ConcurrentHashMap<Long,Room>();
	private final ConcurrentHashMap<String,Room> roomsByName = new ConcurrentHashMap<String,Room>();
	
	private RoomManager() {
		
	}
	
	/**
	 * Gets standalone instance of RoomManager class
	 * @return standalone object
	 */
	public static RoomManager instance() {
		return instance;
	}

	/**
	 * Adds a new room with a specified name and owner
	 * @param name room name
	 * @param connectionId owner connection id
	 * @return unique new room id
	 * @throws ServerException if roow with that name already exists
	 */
	public Long addRoom(String name, Long connectionId) throws ServerException {
		
		synchronized (rooms) {
			if ( roomsByName.get(name) != null ) throw new ServerException(19);
			
			Room room = new Room(connectionId,roomNumber.incrementAndGet(),name);
			
			rooms.put(room.getId(),room);
			roomsByName.put(room.getName(),room);
			
			return room.getId();
		}
		
		
	}

	/**
	 * Performs joining the room by a user
	 * 
	 * @param roomId room to join
	 * @param connectionId joining user
	 * @throws ServerException if room is occupied, owner wants to join or room does not exists
	 */
	public void joinRoom(Long roomId, Long connectionId) throws ServerException {
		
		Room room = rooms.get(roomId);
		
		if (room == null) throw new ServerException(9);
		
		if (room.getFirstPlayer() == connectionId) throw new ServerException(20);
		
		synchronized (room) {
			if (room.getFirstPlayer() == null) throw new ServerException(21);
			if (room.getSecondPlayer() != null) throw new ServerException(22);
			room.setSecondPlayer(connectionId);
		}
		
	}

	/**
	 * Returns room with a specified id
	 * @param roomId
	 * @return
	 */
	public Room getRoom(Long roomId) { 
		return rooms.get(roomId);
	}
	
	/**
	 * Returns all active rooms
	 * @return
	 */
	public Enumeration<Room> getRooms() {
		return rooms.elements();
	}

	/**
	 * Remove room with roomId
	 * @param roomId
	 */
	public void removeRoom(Long roomId) {
		Room room = rooms.get(roomId);
		rooms.remove(roomId);
		roomsByName.remove(room.getName());
	}

	
	/**
	 * Leave room with roomId by player with connectionId 
	 * An appropriate message is send to the opponent.
	 * Room is destroyed and removed from players room list.
	 * 
	 * @param roomId 
	 * @param connectionId
	 * @return flag if this room is supposed to be remove
	 * @throws ServerException if user is not in room with room id or room does not exists
	 */
	public boolean leaveRoom(Long roomId,Long connectionId) throws ServerException {
		Room room = getRoom(roomId);
				
		if (room == null) throw new ServerException(23);
		if (room.getFirstPlayer() != connectionId && room.getSecondPlayer() != connectionId) throw new ServerException(2);
		
		LeavetableCause cause = room.getState() == RoomState.GAME ? LeavetableCause.OPPONENT_LEFT_DURING_GAME : 
																LeavetableCause.OPPONENT_LEFT;
		
		synchronized (room) {

			if ( room.getFirstPlayer() == connectionId ) {
				if (room.getSecondPlayer() != null) {
					User secondPlayer = USER_MANAGER.getUser(room.getSecondPlayer());
					synchronized (secondPlayer) {
						secondPlayer.removeRoom(roomId);
					}
					
					SENDING_ADAPTER.send(new Event(EventType.ENDGAME,
													new String[] { roomId.toString(),LeavetableCause.getCode(cause).toString() }, 
													room.getSecondPlayer()));
				}
				removeRoom(roomId);
			} else {
/*				if (room.getFirstPlayer() != null) {
					SENDING_ADAPTER.send(new Event(EventType.ENDGAME,
						new String[] { roomId.toString(),LeavetableCause.getCode(cause).toString() }, 
						room.getFirstPlayer()));
				}
				
				room.setSecondPlayer(null);*/
				
				if (room.getFirstPlayer() != null) {
					User firstPlayer = USER_MANAGER.getUser(room.getFirstPlayer());
					synchronized (firstPlayer) {
						firstPlayer.removeRoom(roomId);
					}
					
					SENDING_ADAPTER.send(new Event(EventType.ENDGAME,
													new String[] { roomId.toString(),LeavetableCause.getCode(cause).toString() }, 
													room.getFirstPlayer()));
				}
				removeRoom(roomId);
			}
			
		}
		
		return true;
	}
	
	
}
